using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Net.Sockets;

namespace rudp
{
    public delegate void ReceiptRequestDelivered( CommunicationState state );
	
    public class DeliveryQueue
	{
        public ReceiptRequestDelivered OnReceiptRequestDelivered{ get; set; }

        private ManualResetEventSlim      			dataAvailable;
		private Thread 			     	  			workerThread;
		private ConcurrentQueue<CommunicationState> queue;

        public DeliveryQueue ( ReceiptRequestDelivered callback )
		{
            this.OnReceiptRequestDelivered = callback;
		}
        		
		public void Initialize()
		{
			this.dataAvailable = new ManualResetEventSlim ();
			this.queue = new ConcurrentQueue<CommunicationState> ();

			ThreadStart threadStart = new ThreadStart (this.ProcessQueue);

			this.workerThread = new Thread (threadStart);
			this.workerThread.Start ();
		}

        public void Add( CommunicationState communicationState )
        {
            this.queue.Enqueue( communicationState );
            this.dataAvailable.Set();
        }

        private int CalculateBackoff()
        {
            return 0;
        }

		private void ProcessQueue()
		{
            for(;;)
			{
                this.dataAvailable.Wait();

                CommunicationState state = null;

				this.queue.TryDequeue (out state);
			
				Message messagePacket = state.Packets [state.Index];
								
				state.CurrentStep = Step.DeliveringPackets;

				// Mark the last packet to request a receipt
				if (state.Packets.IsLastMessage (messagePacket)) 
				{
					messagePacket.Flags = MessageFlags.Data | MessageFlags.ReceiptRequest;
					state.CurrentStep = Step.DeliveringReceiptRequest;
				}

				byte[] data = messagePacket.ToByteArray();
							
				IAsyncResult result = state.Socket.BeginSendTo(data, 0, data.Length, SocketFlags.None, state.RemoteHost, this.OnSend, state);

				this.dataAvailable.Reset ();
			}
		}

		public void OnSend (IAsyncResult result)
		{		
			CommunicationState state = (CommunicationState)result.AsyncState;

			state.BytesSent = state.Socket.EndSendTo (result);

            if (state.CurrentStep == Step.DeliveringPackets)
            {
                state.Index++;
                this.Add(state);
            }
            else if (state.CurrentStep == Step.DeliveringReceiptRequest)
            {
                state.CurrentStep = Step.DeliveredReceiptRequest;
                this.OnReceiptRequestDelivered(state);
            }
		}
	}
}

